/* ws2812b.c */
#include "ws2812b.h"


#define WS2812B_SPI_BAUDRATE    4000000u /* 4mhz. */



#define WS2812_COLOR_R(x)       (((uint32_t)(x) <<  8) & 0x00FF00)
#define WS2812_COLOR_G(x)       (((uint32_t)(x) << 16) & 0xFF0000)
#define WS2812_COLOR_B(x)       (((uint32_t)(x) <<  0) & 0x0000FF)



volatile bool ws2812b_dma_channel_busy_flag = false;

DMA_ChannelTransStruct_Type ws2812b_dma_channel_init =
{
    .SourceAddr = 0u, /* tbd. */
    .SourceAddrOffsetOnTrans = 1u,
    .SourceTransDataWidth = DMA_TransDataWidth_8b,
    .SourceAddrOffsetOnMajorLoop = 0u, /* no need turn around automatically. */
    
    .DestAddr = (uint32_t)(&BOARD_WS2812B_SPI_PORT->DATA), /* spi tx data reg. */
    .DestAddrOffsetOnTrans = 0u, /* do not move. */
    .DestTransDataWidth = DMA_TransDataWidth_8b,
    .DestAddrOffsetOnMajorLoop = 0, /* do not move. */
    
    .MinorLoopByteCount = 1u,
    .MajorLoopCount = 0u, /* tbd. */
    
    .EnableInterruptOnMajorLoopDone = true,
    .EnableInterruptOnMajorLoopHalfDone = false,
    .EnableInterruptOnMinorLoopDone = false,
        
    .EnableAutoDisableChannelReqOnMajorLoopDone = true, /* disable the req when the trans task is done. */
        
    .EnableReloadRamConf = false,
};

void ws2812b_init(void)
{
    /* setup spi master. */
    SPI_MasterInit_Type spi_master_init;
    spi_master_init.ClkSrcFreq = BOARD_WS2812B_SPI_CLK_SRC_HZ;
    spi_master_init.BaudrateBaseFreq = WS2812B_SPI_BAUDRATE;
    spi_master_init.EnableHighzOutputOnIdle = true;
    spi_master_init.RxFifoWatermark = 1u;
    spi_master_init.TxFifoWatermark = 1u;
    SPI_InitMaster(BOARD_WS2812B_SPI_PORT, &spi_master_init);

    SPI_XferChannelConf_Type spi_channel_init;
    spi_channel_init.Prescale = SPI_Prescale_Div1; /* 16mhz / 4 = 4mhz. */
    spi_channel_init.PolarityPhase = SPI_PolarityPhase_Alt0;
    spi_channel_init.PcsActiveMode = SPI_PcsActiveMode_Low;
    spi_channel_init.DataBitOrder = SPI_DataBitOrder_MSB;
    spi_channel_init.EnableByteSwap = false;
    spi_channel_init.FrameBits = 8u;
    spi_channel_init.XferMode = SPI_XferMode_TxOnly;
    SPI_ActiveXferChannel(BOARD_WS2812B_SPI_PORT, BOARD_WS2812B_SPI_CHANNEL, &spi_channel_init);

    SPI_EnableTxDma(BOARD_WS2812B_SPI_PORT, true);    /* enable the dma. */
    
    /* setup the dma channel. */
    DMA_SetChannelTrigger(DMA, BOARD_DMA_WS2812B_SPI_TX_CHANNEL, BOARD_DMA_WS2812B_SPI_TX_REQ);
    NVIC_EnableIRQ(BOARD_DMA_WS2812B_IRQn);

    /* setup the lptmr to show the leds. */
    LPTMR_TimeCounterInit_Type lptmr_timer_init;
    lptmr_timer_init.ClkDiv = LPTMR_ClkDiv_256; /* 2mhz / 256 = 10khz. */
    lptmr_timer_init.ModVal = 100; /* 10hz. */
    LPTMR_InitTimeCounter(BOARD_WS2812B_LPTMR_PORT, &lptmr_timer_init);
    
    NVIC_EnableIRQ(BOARD_WS2812B_LPTMR_IRQn);
    LPTMR_EnableInterrupts(BOARD_WS2812B_LPTMR_PORT, LPTMR_FLAG_MATCH_MOD);
    
    LPTMR_StartCounter(BOARD_WS2812B_LPTMR_PORT);
}

/* 24 bits -> 24 bytes. */
void ws2812b_prepare_led_color(uint8_t * buff, uint32_t ledidx, uint32_t color)
{
    for (uint32_t i = 0u; i < WS2812B_COLOR_BIT_NUM; i++)
    {
        if ( (color & (1u << (WS2812B_COLOR_BIT_NUM - i - 1))) != 0u )
        {
            buff[i + ledidx * WS2812B_COLOR_BIT_NUM] = WS2812B_BIT1_CODE;
        }
        else
        {
            buff[i + ledidx * WS2812B_COLOR_BIT_NUM] = WS2812B_BIT0_CODE;
        }
    }
}

/* launch the dma. */
void ws2812b_reflesh(uint8_t * buff, uint32_t size)
{
    /* skip the update when the dma is busy. */
    if (ws2812b_dma_channel_busy_flag)
    {
        return;
    }
    
    ws2812b_dma_channel_busy_flag = true; /* request the dma resource. */
    
    /* restart the dma channel. */
    ws2812b_dma_channel_init.SourceAddr = (uint32_t)buff;
    ws2812b_dma_channel_init.MajorLoopCount = size;
    DMA_SetChannelTransStruct(DMA, BOARD_DMA_WS2812B_SPI_TX_CHANNEL, &ws2812b_dma_channel_init);

    DMA_EnableChannelReqs(DMA, 1u << BOARD_DMA_WS2812B_SPI_TX_CHANNEL);
}

/* isr for dma task done. */
void BOARD_DMA_WS2812B_IRQHandler(void)
{
    if ( (DMA_GetMajorLoopDoneChannels(DMA) & (1u << BOARD_DMA_WS2812B_SPI_TX_CHANNEL)) != 0u )
    {
        ws2812b_dma_channel_busy_flag = false;
        DMA_ClearMajorLoopDoneChannels(DMA, (1u << BOARD_DMA_WS2812B_SPI_TX_CHANNEL) );
    }
}


#define APP_WS2812B_LED_COUNT    8u
uint32_t app_ws2812b_led_index = 0u;

#define APP_WS2812B_BUFF_SIZE    (APP_WS2812B_LED_COUNT * WS2812B_COLOR_BIT_NUM)

uint32_t app_ws2812b_buff_raw[APP_WS2812B_BUFF_SIZE / 4]; /* 192. */
uint8_t * app_ws2812b_buff = (uint8_t * )app_ws2812b_buff_raw;

/* isr for lptmr task done. */


#define APP_WS2812B_COLOR_ARR_COUNT  3u
uint32_t app_ws2812b_color_arr[APP_WS2812B_COLOR_ARR_COUNT] =
{
    WS2812_COLOR_R(0x1f),
    WS2812_COLOR_G(0x1f),
    WS2812_COLOR_B(0x1f),
};
uint32_t app_ws2812b_color_index = 0u;

void BOARD_WS2812B_LPTMR_IRQHandler(void)
{
    uint32_t flags = LPTMR_GetStatusFlags(BOARD_WS2812B_LPTMR_PORT);
    
    if ( (flags & LPTMR_FLAG_MATCH_MOD) != 0u )
    {
    
        /* show the ws2812b. */
        memset(app_ws2812b_buff, WS2812B_BIT0_CODE, APP_WS2812B_BUFF_SIZE);
        ws2812b_prepare_led_color(app_ws2812b_buff, app_ws2812b_led_index, app_ws2812b_color_arr[app_ws2812b_color_index]);
        ws2812b_reflesh(app_ws2812b_buff, APP_WS2812B_BUFF_SIZE);
        
        /* update index. */
        app_ws2812b_led_index = (app_ws2812b_led_index + 1u) % APP_WS2812B_LED_COUNT;
        if (app_ws2812b_led_index == 0u)
        {
            app_ws2812b_color_index = (app_ws2812b_color_index + 1u) % APP_WS2812B_COLOR_ARR_COUNT;
        }
        
        LPTMR_ClearStatusFlags(BOARD_WS2812B_LPTMR_PORT, LPTMR_FLAG_MATCH_MOD);
    }
}

    
/* EOF. */

